# -*- coding: utf-8 -*-
"""
:mod:`nubox.char`
=================

Provides the ``NuBox`` sub-class hierarchy to store characters i.e. ``bytes``. 

"""
from __future__ import (division, with_statement, absolute_import, generators,
                         nested_scopes)
from array import array
from itertools import chain

from .core import NuBoxScalar, NuBoxVector, NuBoxArray, NuBoxCube
from .shim import int, long, bytes
from .const import CHAR_DUMMY
from .core import NdArray


class Char(NuBoxScalar):
    """
    A scalar ``NuBox`` to hold characters as ``bytes``.
    
    """
    clstypecode = 'b'
    clsdummy = CHAR_DUMMY

    def __init__(self, *args, **kwargs):
        if args:
            data = args[0]
        elif 'data' in kwargs:
            data = kwargs.pop('data')
        else:
            data = None
        if data:
            try:
                item = data[0]
            except TypeError:
                raise TypeError("data must be iterable")
            if isinstance(item, bytes) and len(item) == 1:
                # creating a byte-array from bytes is really fast
                data = array('b', "".join(data))
            super(Char, self).__init__(data, *args[1:], **kwargs)
        else:
            super(Char, self).__init__(**kwargs)

    def __str__(self):
        name = self.__class__.__name__
        return "".join((name, '(', "".join([chr(i) for i in self.data]), ')'))
        # return "".join(str([self[i]) for i in range(len(self))])
        # return "".join((name , '(', ), ')'))


    def __getitem__(self, index):
        ret = super(Char, self).__getitem__(index)
        return self._tochr(ret)

    def __setitem__(self, index, value):
        super(Char, self).__setitem__(index, self._toord(value))

    def _tochr(self, ords):
        if isinstance(ords, (int, long)):
            return chr(ords)
        elif isinstance(ords, bytes):
            return ords
        elif isinstance(ords, NdArray):
            return ords
        elif isinstance(ords[0], bytes):
            return ords
        else:
            try:
                return tuple([chr(o) for o in ords])
            except Exception:
                raise ValueError("cannot convert to chars")

    def _toord(self, chars):
        if isinstance(chars, bytes) and len(chars) == 1:
            return ord(chars)
        elif isinstance(chars, int):
            return chars
        elif isinstance(chars, NdArray):
            return chars
        elif isinstance(chars[0], int):
            return chars
        else:
            try:
                return tuple([ord(c) for c in chars])
            except Exception:
                raise ValueError("cannot convert to ords")

    def get_item(self, index):
        """
        Same as in parent class ``NuBox``
        
        """
        ret = super(Char, self).get_item(index)
        return self._tochr(ret)

    def set_item(self, index, value):
        """
        Same as in parent class ``NuBox``
        
        """
        value = self._toord(value)
        super(Char, self).set_item(index, value)

    def iter_item(self):
        """
        Same as in parent class ``NuBox``
        
        """
        for item in super(Char, self).iter_item():
            yield self._tochr(item)

    def fill_item(self, value):
        """
        Same as in parent class ``NuBox``
        
        """
        value = self._toord(value)
        super(Char, self).fill_item(value)

    def str(self, method, *args, **kwargs):
        """
        Applies a ``str`` method. If the method returns a ``str`` of the same 
        length modifies data in-place, else returns the result. Involves one or
        two copies of the raw data.
        
        Arguments:
        
          - method(``str``) Any method of a ``str`` instance e.g. "upper"
          
        Additional arguments and keyworded arguments are passed to the called
        "method".
        
        """
        try:
            self_str = self.data.tostring()
        except AttributeError:
            self_str = str(self.data)
        res = getattr(self_str, method)(*args, **kwargs)
        if isinstance(res, bytes) and len(res) == len(self):
            res = self._toord(res)
            self._setdata((self.store, self.typecode, res, True))
        else:
            return res

    def recode(self, code, inplace=True):
        """
        This re-codes (translates) a CharX given a "code" dictionary. Returns
        a new instance if "inplace" is ``False``. 
        
        Arguments:
        
          - code(``dict``) Translation dictionary.
              
        """
        obj = self if inplace else self.new()
        for i, item in enumerate(self.iter_item()):
            obj.set_item(i, code[item])
        return obj

    def tobytes(self):
        """
        Returns the all data as a ``byte`` string.
        
        """
        try:
            bytes = self.data.tostring()
        except AttributeError:
            bytes = "".join([chr(b) for b in self.data])
        return bytes


class CharVector(Char, NuBoxVector):
    """
    A vector ``Chars``.
    
    """
    clschild = Char

    def _keep_or_omit(self, values, mode="keep"):
        # returns smaller self
        ord_values = []
        for child in values:
            ord_child = []
            for element in child:
                if isinstance(element, bytes):
                    ord_child.append(ord(element))
                else:
                    ord_child.append(element)
            ord_values.append(tuple(ord_child))

        keep = (mode == "keep")
        omit = (mode == "omit")
        ch_data = self.data[0:0]
        ch_kwargs = []
        for i, child in enumerate(self.iter_child(fast=True)):
            value = tuple(child.data)
            if (keep and (value in ord_values)) or \
               (omit and (value not in ord_values)):
                ch_data.extend(child.data)
                ch_kwargs.append(self._childkwargs(i))
        shape = (len(ch_kwargs),) + self.shape[1:]
        return self.new(ch_data, shape=shape, childrenkwargs=ch_kwargs)

    def keep(self, values):
        """
        Returns a new instance, which keeps only **children** with **element** 
        tuples in "values". An element ``tuple`` is constructed from the
        whole "data" property of the **child**.
        
        Arguments:
        
          - values(sequence of **element** ``tuples``) a sequence of tuples. 
            Each tuple should be the same length as a **child** and hold 
            **elements**.
             
        """
        return self._keep_or_omit(values, "keep")

    def omit(self, values):
        """
        Returns a new instance, which omit **children** with **element** 
        ``tuples`` in "values". An element ``tuple`` is constructed from the
        whole "data" property of the **child**. 
        
        Arguments:
        
          - values(sequence of **element** ``tuples``) a sequence of tuples. 
            Each tuple should be the same length as a **child** and hold 
            **elements**.
        
        """
        return self._keep_or_omit(values, "omit")

    def find(self, ktup):
        """
        Determines the start positions of all occurrences of ktup in ``self``.
        The "ktup" argument should be a sequence of **children** data in the 
        for of ``tuples``.
        
        Arguments:
        
            - ktup(sequence of **elements**) in the case of ``Char`` 
              sub-classes elements are ``str`` of lenght 1
        
        """
        # this flattens and converts "" -> int
        flat_ktup = tuple(chain(*ktup))
        ord_ktup = self._toord(flat_ktup)
        # determine the size of child   
        step = self._childrenstep()

        if (len(ord_ktup) % step) != 0:
            raise ValueError("ktup length is not multiple of child size")
        # number of additional steps
        bigstep = int((len(ord_ktup) / step) - 1)

        childrenbounds = self._childrenbounds(step=step)
        indices = []
        for i, (start, stop) in enumerate(childrenbounds):
            if tuple(self.data[start:stop + bigstep]) == ord_ktup:
                indices.append(i)
        return tuple(indices)


class CharArray(CharVector, NuBoxArray):
    """
    An array of ``Chars`` (vector of ``CharVectors``).
    
    """
    clschild = CharVector


class CharCube(CharArray, NuBoxCube):
    """
    A cube ``Chars`` (vector of ``CharArrays``).
    
    """
    clschild = CharVector


Char.clsparent = CharVector
CharVector.clsparent = CharArray
CharArray.clsparent = CharCube

del NuBoxScalar, NuBoxVector, NuBoxArray, NuBoxCube
